
import datetime
from typing import Union
from backend.cews.services import RepairService
from backend.cews.models import Log, Parts, PartsEDoc, PartsReplacement, RepairOrder, RepairOrderEDoc, RepairOrderPart, RepairOrderPartVersion, RepairOrderTask, StockItem, StockItemEDoc, VersionMaster,RepairOrderPartReplacement, RepairOrderImprovementWork, PartsImprovementWork, RepairOrderRepairDetails
from backend.cews.services.utils import all_related_stock_code_by_stock_code
from django.db import transaction
from django.contrib.auth import authenticate
from backend.gnsuser.models import GnsUser
from logger.createlogger import create_logger
import json

logger = create_logger('CheckOutService')

# def check_parts_version(parts:Parts):
    
#     stock_code = parts.stock_code
#     versions = VersionMaster.objects.filter(stock_code=stock_code)
    
#     sw_vers = [v.sw_ver for v in versions]
#     hw_vers = [v.hw_ver for v in versions]
    
#     # sw_result = {'outstanding':0,'message':[]}
#     # hw_result = {'outstanding':0,'message':[]}    
#     result = {'outstanding':0,'message':[]}
    
#     if parts.sw_ver not in sw_vers:
#         result['outstanding'] += 1
#         result['message'].append(f'Software version of {parts.serial_no} is not latest.')
    
#     if parts.hw_ver not in hw_vers:
#         result['outstanding'] += 1
#         result['message'].append(f'Hardware version of {parts.serial_no} is not latest.')
    
#     return result

# def check_parts_edoc(parts:Parts):
    
#     outstanding = 0
#     message = []
    
#     ## stock_code
#     stock_code = parts.stock_code
    
#     ## get parts edoc
#     parts_edocs = PartsEDoc.objects.filter(parts=parts)
#     parts_edocs_dict = {p.stock_code+p.doc_name+p.version:p for p in parts_edocs}
     
#     ## get stock item edoc
#     stock_item_edocs = StockItemEDoc.objects.filter(stock_item__stock_code=stock_code,invalid=0)
    
#     for stock_item_edoc in stock_item_edocs:
#         key = stock_item_edoc.stock_code+stock_item_edoc.doc_name+stock_item_edoc.version        
#         parts_edoc = parts_edocs_dict.get(key,False)
#         if parts_edoc is False:
#             outstanding += 1
#             message.append(f"{stock_item_edoc.stock_code} {stock_item_edoc.doc_name} {stock_item_edoc.version} is not complete.") 
    
#     return {
#         'outstanding':outstanding,
#         'message':message,
#     }

# def check_parts_hardtime_replacement(parts:Parts):
#     outstanding = 0
#     message = []
    
#     ## get parts repalcement
#     parts_replacements = PartsReplacement.objects.filter(parts=parts)
    
#     for parts_replacement in parts_replacements:
#         if parts_replacement.is_critical:
#             outstanding +=1
#             message.append(f'{parts_replacement.component} is critical and not replaced.')

#     return {
#         'outstanding':outstanding,
#         'message':message
#     }
    
# def check_parts_status(parts:Parts):
#     status = {
#         # 'software':{
#         #     "outstanding":0,
#         #     "message":[]
#         # },
#         # 'hardware':{
#         #     "outstanding":0,
#         #     "message":[]
#         # },
#         'version':{
#             "outstanding":0,
#             "message":[]
#         },
#         'edoc':{
#             "outstanding":0,
#             "message":[]
#         },
#         'hard_time_replacement':{
#             "outstanding":0,
#             "message":[]
#         } 
#     }
#     status['version'] = check_parts_version(parts)
#     status['edoc'] = check_parts_edoc(parts)
#     status['hard_time_replacement'] = check_parts_hardtime_replacement(parts)
#     return status


def check_all_status(repair_order:Union[int,RepairOrder]):
    
    status = {
        "part_detail":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "current_task":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "software":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "hardware":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "edoc":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "hard_time_replacement":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "improvement_work":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "final_check":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        },
        "fail_part":{
            "outstanding":0,
            "message":[],
            "log_parts": []
        }
    }
    
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
    # part_detail
    status['part_detail'] = check_repair_order_part_detail(repair_order)
    
    # check task
    status['current_task'] = check_repair_order_task_done(repair_order,RepairOrderTask.RoleType.TESTER)
    
    # sw and hw version
    status = {**status,**check_version(repair_order)}
    
    # edoc
    status['edoc'] = check_edoc(repair_order)

    # improvement work
    status['improvement_work'] = check_imp_work(repair_order)
    
    # check final check
    status['final_check'] = check_repair_order_final_check_task_done(repair_order,RepairOrderTask.RoleType.CHECKER)
    
    ## TODO: TEST
    status['hard_time_replacement'] = check_replacement(repair_order)

    # check final check Test Result
    status['test_result'] = check_repair_order_test_result(repair_order)

    for key in status:
        if key =="fail_part" : continue
        if status[key]['outstanding'] == 0:
            continue
        for k in status[key]:
            if k == 'log_parts':
                for part in status[key][k]: 
                    if part['serial_no'] == '': continue
                    status["fail_part"][part['stock_code']+part['serial_no']] = 'fail'
         
    
    return status

def check_repair_order_test_result(repair_order:Union[int,RepairOrder]):
    outstanding = 0
    messages = []
    log_parts = []

    repair_order_detail = RepairOrderRepairDetails.objects.get(repair_order=repair_order)
    if repair_order_detail.test_result == '' or repair_order_detail.test_result is None:
        outstanding += 1
        messages.append(f"Test result in final check is not completed")

    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }  

def check_replacement(repair_order:Union[int,RepairOrder]):
    outstanding = 0
    messages = []
    log_parts = []
    
    repair_order_parts = RepairOrderPart.objects.filter(repair_order=repair_order)

    parts_dict = {
        p.stock_code : p
        for p in repair_order_parts
    }
    rop_replacements = RepairOrderPartReplacement.objects.select_related('repair_order_part').filter(repair_order_part__in=repair_order_parts,deleted=False)
    
    for r in rop_replacements:
        r:RepairOrderPartReplacement
        if r.is_critical and (r.replaced is False or r.replaced is None):
            print('yea~')
            outstanding+=1
            rop:RepairOrderPart = r.repair_order_part
            messages.append(f'{parts_dict[rop.stock_code].description}: {r.component} is critical and not replaced.')
            log_parts.append({"stock_code":rop.stock_code,"serial_no":rop.serial_no})
        # else:
        #     if r.period ==0 and r.replaced == False:
        #         outstanding+=1
        #         rop:RepairOrderPart = r.repair_order_part
        #         messages.append(f'{rop.stock_code} {r.component} is critical and not replaced.')
        
        
    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }   
    
def check_edoc(repair_order:Union[int,RepairOrder]):
    outstanding = 0
    messages = []
    log_parts = []
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
    repair_order_parts = repair_order.get_repair_order_part()

    parts_dict = {
        p.stock_code : p
        for p in repair_order_parts
    }
    edocs = RepairOrderEDoc.objects.filter(repair_order=repair_order)
    
    for e in edocs:
        if e.complete == False:
            outstanding += 1
            messages.append(f'{parts_dict[e.stock_code].description}: Serial no: {e.serial_no}. Edoc: {e.doc_name} is not complete.')
            log_parts.append({"stock_code":e.stock_code,"serial_no":e.serial_no})
    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }

def check_version(repair_order:Union[int,RepairOrder]):
    '''
        Check software and hardware version of repair order
        
        Return:
        ```python
            {
                'software':{
                    'outstanding':0,
                    'message':[],
                },
                'hardware':{
                    'outstanding':0,
                    'message':[],
                },
            }
        ```
    '''
    status = {
        'software':{
            'outstanding':0,
            'message':[],
            "log_parts": []
        },
        'hardware':{
            'outstanding':0,
            'message':[],
            "log_parts": []
        },
    }
    
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
    
    rop_version = RepairOrderPartVersion.objects.filter(repair_order=repair_order).select_related('repair_order_part')
    stock_codes = [r.stock_code for r in rop_version]
    ver_dict = VersionMaster.get_version_dict(stock_codes)
    for r in rop_version:
        latest_ver = ver_dict.get(r.stock_code)

        ## must have version when create stock master 
        """ if not latest_ver:
            status['software']['outstanding'] += 1
            status['software']['message'].append(f'Software version of {r.stock_code} is not found.')
            status['software']['log_parts'].append({"stock_code":r.stock_code,"serial_no":r.serial_no})
            status['hardware']['outstanding'] += 1
            status['hardware']['message'].append(f'Hardware version of {r.stock_code} is not found.')
            status['hardware']['log_parts'].append({"stock_code":r.stock_code,"serial_no":r.serial_no})
            continue """
        
        if isinstance(latest_ver, list):
            sw_vers = [l.sw_ver.replace(" ", "") for l in latest_ver ]
            hw_vers = [l.hw_ver.replace(" ", "") for l in latest_ver ]
            sws = r.sw_ver.split(',')
            hws = r.hw_ver.split(',')
            has_sw = False
            has_hw = False

            for sw in sws:
                if sw.replace(" ", "") in sw_vers:
                    has_sw = True

            # if '' in sw_vers:
            #     has_sw = True


            if has_sw is False:
                status['software']['outstanding'] += 1
                status['software']['message'].append(f'Software version of {r.stock_code} is not latest.')
                status['software']['log_parts'].append({"stock_code":r.stock_code,"serial_no":r.serial_no})
            
            for hw in hws:
                if hw.replace(" ", "") in hw_vers:
                    has_hw = True
                    
            # if '' in hw_vers:
            #     has_hw = True


            if has_hw is False:
                status['hardware']['outstanding'] += 1
                status['hardware']['message'].append(f'Hardware version of {r.stock_code} is not latest.')
                status['hardware']['log_parts'].append({"stock_code":r.stock_code,"serial_no":r.serial_no})
            
            continue
        
            
    # status['software']['message'] = '\n'.join(status['software']['message'])
    # status['hardware']['message'] = '\n'.join(status['hardware']['message'])  
    
    return status

def check_repair_order_part_detail(repair_order:Union[int,RepairOrder]):
    outstanding = 0
    messages = []
    log_parts = []
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
        
    parent = repair_order.primary_part
    repair_order_parts = repair_order.get_repair_order_part()
    
    stock_code = parent.stock_code
    
    stock_codes = all_related_stock_code_by_stock_code(stock_code)

    parts_dict = {
        p.stock_code : p
        for p in repair_order_parts
    }
    
    rop_stock_codes = [r.stock_code for r in repair_order_parts if r.serial_no!='']
    
    for sc in stock_codes:
        if sc not in rop_stock_codes:
            if sc in parts_dict:
                messages.append(f'Repair Order Part {parts_dict[sc].description}, serial_no: {parts_dict[sc].serial_no}  is not found in repair order.')
                log_parts.append({"stock_code":repair_order.primary_part.stock_code,"serial_no":repair_order.primary_part.serial_no})
                outstanding += 1
    
    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }
        

def check_repair_order_task_done(repair_order:Union[int,RepairOrder],role):
    
    repair_order_tasks = RepairOrderTask.objects.filter(repair_order=repair_order,role=role,cur_tab=True).select_related('stock_item_task')
    
    outstanding = 0
    messages  = []
    log_parts = []
    for r in repair_order_tasks:
        if r.role_result != r.ResultType.PASS:
            outstanding += 1
            messages.append(f'{r.task_name} is not done.')
            log_parts.append({"stock_code":r.stock_code,"serial_no":r.serial_no})
    
    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }

def check_repair_order_final_check_task_done(repair_order:Union[int,RepairOrder],role):
    
    repair_order_tasks = RepairOrderTask.objects.filter(repair_order=repair_order,role=role,final_tab=True).select_related('stock_item_task')
    
    outstanding = 0
    messages  = []
    log_parts = []
    
    for r in repair_order_tasks:
        if r.role_result != r.ResultType.PASS:
            outstanding += 1
            messages.append(f'{r.task_name} is not done.')
            log_parts.append({"stock_code":r.stock_code,"serial_no":r.serial_no})
    
    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }
    
def update_edoc_version(rops, repair_order):
    repair_order_edocs = RepairOrderEDoc.objects.filter(repair_order=repair_order)
    parts_edocs  = PartsEDoc.objects.filter(serial_no__in=[r.serial_no for r in rops])
    parts_edocs_dict = {p.stock_code+p.doc_name+p.version+p.serial_no:p for p in parts_edocs}
    ## prepare stock
    stocks = StockItem.objects.filter(stock_code__in=[r.stock_code for r in repair_order_edocs])  
    stocks_dict = {s.stock_code:s for s in stocks}
    p_to_update = []
    p_to_create = []
    
    for r in repair_order_edocs:
        if not r.parts:
            print(f'No parts for {r.stock_code}-{r.doc_name}-{r.version}-{r.serial_no}')
            continue
        
        p = parts_edocs_dict.get(r.stock_code+r.doc_name+r.version+r.serial_no,False)
        
        if p:
            print(f"found P but P is False {p}, Complete {r.stock_code}-{r.doc_name}-{r.version}-{r.serial_no}")
            p.complete = r.complete
            p_to_update.append(p)
            
        if not p:
            print(f"not found P {r.stock_code}-{r.doc_name}-{r.version}-{r.serial_no}, create new one")
            p = PartsEDoc()
            stock = stocks_dict.get(r.stock_code)
            p.stock_item = stock
            p.parts = r.parts
            p.stock_code = stock.stock_code
            p.serial_no = r.serial_no
            p.doc_name = r.doc_name
            p.description = r.description
            p.complete = r.complete
            p.invalid = 0
            p.file = r.file_url
            p.version = r.version
            p.deleted = False
            
            p_to_create.append(p)
            
    if p_to_update:
        PartsEDoc.objects.bulk_update(p_to_update,['complete'])
    if p_to_create:
        PartsEDoc.objects.bulk_create(p_to_create)


def update_improvement_work_version(rops, repair_order):
    repair_order_imp_works = RepairOrderImprovementWork.objects.filter(repair_order=repair_order)
    parts_imp_work  = PartsImprovementWork.objects.filter(serial_no__in=[r.serial_no for r in rops])
    parts_imp_work_dict = {p.stock_code+p.doc_name+p.version+p.serial_no:p for p in parts_imp_work}
    ## prepare stock
    stocks = StockItem.objects.filter(stock_code__in=[r.stock_code for r in repair_order_imp_works])  
    stocks_dict = {s.stock_code:s for s in stocks}
    p_to_update = []
    p_to_create = []
    
    print('parts_imp_work_dict', parts_imp_work_dict)

    for r in repair_order_imp_works:
        if not r.parts:
            print(f'No parts for {r.stock_code}-{r.doc_name}-{r.version}-{r.serial_no}')
            continue
        
        p = parts_imp_work_dict.get(r.stock_code+r.doc_name+r.version+r.serial_no,False)
        print('parts_imp_work part', p)
        
        if p:
            print(f"found P but P is False {p}, Complete {r.stock_code}-{r.doc_name}-{r.version}-{r.serial_no}")
            p.complete = r.complete
            p_to_update.append(p)
            
        if not p:
            print(f"not found P {r.stock_code}-{r.doc_name}-{r.version}-{r.serial_no}, create new one")
            p = PartsImprovementWork()
            stock = stocks_dict.get(r.stock_code)
            p.stock_item = stock
            p.parts = r.parts
            p.stock_code = stock.stock_code
            p.serial_no = r.serial_no
            p.doc_name = r.doc_name
            p.description = r.description
            p.complete = r.complete
            p.invalid = 0
            p.file = r.file_url
            p.version = r.version
            p.deleted = False
            
            p_to_create.append(p)
    print('p_to_create', p_to_create)
    print('p_to_update', p_to_update)

    if p_to_update:
        PartsImprovementWork.objects.bulk_update(p_to_update,['complete'])
    if p_to_create:
        PartsImprovementWork.objects.bulk_create(p_to_create)

def update_hardtime_replacement(repair_order:Union[int,RepairOrder]):
    """
        Update hardtime replacement last replaced date from repair order part replacement
    """
    
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
    
    repair_order_parts = RepairOrderPart.objects.filter(repair_order=repair_order)
    ## update parts hardtime replacement from repair order part replacement
    repair_order_part_replacements = RepairOrderPartReplacement.objects.filter(repair_order=repair_order)
    
    ## dict of repair order part replacement using order part id as key
    # repair_order_part_replacements_dict = { 
    #     r.repair_order_part.id:r
    #     for r in repair_order_part_replacements
    # }
    # print('repair_order_part_replacements_dict', repair_order_part_replacements_dict)
    
    parts_replacement_to_update = []
    
    for repair_order_part in repair_order_parts:
        
        parts = repair_order_part.part
        ## get repair order part replacement
        # repair_order_part_replacement = repair_order_part_replacements_dict.get(repair_order_part.id,False)
        for repair_order_part_replacement in repair_order_part_replacements:
            if repair_order_part_replacement.complete is False:
            ## no need to update
                continue
        # if repair_order_part_replacement is False:
        #     continue
        
        # ## check is repalced
        # if repair_order_part_replacement.complete is False:
        #     ## no need to update
        #     continue
        
            ## get parts replacement
            parts_replacement = PartsReplacement.objects.filter(parts=parts,component=repair_order_part_replacement.component,period=repair_order_part_replacement.period).first()
            if not parts_replacement:
                logger.debug('Parts replacement not found, skipping')
                continue
            
            parts_replacement.last_replaced = repair_order_part_replacement.replaced
            
            parts_replacement_to_update.append(parts_replacement)
    PartsReplacement.objects.bulk_update(parts_replacement_to_update,['last_replaced'])

@transaction.atomic
def job_complete(repair_order:Union[int,RepairOrder],user,force=False,qrcode=None,username=None,password=None):
    RepairService.update_repair_order_last_action_date(repair_order)
    
    ## user and permission checking
    if qrcode:
        gnsuser = GnsUser.objects.filter(qrcode=qrcode).first()
        if not gnsuser:
            raise ValueError('User not found.') 
        user = gnsuser.user
        
    if username and password:
        logger.debug(f'Job Complete authenticate by using other user {username}')
        myuser = authenticate(username=username,password=password)
        if myuser:
            logger.debug(f'Job Complete by other user {myuser}')
            user = myuser
        else:
            raise ValueError('Unable to authenticate with provided credentials.')
        
    if force:
        # check user permission
        if not user.has_perm('cews.can_force_job_complete'):
            raise ValueError('User has no permission to force job complete.')
    
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
    
    
    ## check is job complete
    ## get primary part
    primary_part = repair_order.primary_part
    
    if primary_part.status == repair_order.STATUS.COMPLETE:
        raise ValueError('Job is already complete.')
    
    if primary_part.status == repair_order.STATUS.FORCE_COMPLETE:
        raise ValueError('Job is already force complete.')
    
    status = check_all_status(repair_order)
    
    ## check outstanding items 
    outstanding = 0
    for k,v in status.items():
        if k == 'fail_part': continue
        outstanding += v['outstanding']
    
    if outstanding > 0 and not force:
        raise ValueError('The tasks are not all passed/ completed.')
        # raise ValueError('Job is not in complete status.')
    
    ## update parts version
    repair_order_part_versions = RepairOrderPartVersion.objects.filter(repair_order=repair_order).select_related('repair_order_part')
    
    for r in repair_order_part_versions:
        ## check is part exisit, if no part it means it is defitment, no need to update part version
        
        _part = r.repair_order_part.part
        
        if _part:
            # update repair order part version
            r.repair_order_part.sw_ver = r.sw_ver
            r.repair_order_part.hw_ver = r.hw_ver
            r.repair_order_part.save()
            
            # update part version
            _part.sw_ver = r.sw_ver
            _part.hw_ver = r.hw_ver
            _part.save()
        
        ## print difference
        
    ## get all repair order part
    rops = repair_order.get_repair_order_part()
    
    ## update edoc version
    update_edoc_version(rops, repair_order)
        
    ## update improvement work version
    update_improvement_work_version(rops, repair_order)


    ## update hardtime replacement
    update_hardtime_replacement(repair_order)

    ## TODO: CHECK IS IT NECESSARY TO UPDATE PARTS RELATION
    ## update part relation
    # part:Parts
    # part = primary_part.part
    
    # serial_nos = []
    # for r in rops:
    #     if r.is_parent:
    #         continue
    #     serial_nos.append(r.serial_no)
    
    # child_parts = Parts.objects.filter(serial_no__in=serial_nos)
    
    # parts_to_save = []
    # for p in child_parts:
    #     if p .is_parent:
    #         continue
    #     p.parent_part = part
    #     parts_to_save.append(p)
        
    # Parts.objects.bulk_update(parts_to_save,['parent_part'])

    ## update fitment relation

    check_relation_and_update_fitment(primary_part, repair_order, user)
        
    ## update repair order part status
    
    rops_to_save = []
    SELECTED_STATUS = repair_order.STATUS.COMPLETE if not force else repair_order.STATUS.FORCE_COMPLETE
    
    for r in rops:
        r.status = SELECTED_STATUS
        r.complete_datetime = datetime.datetime.now()
        r.complete_by = user
        rops_to_save.append(r)
        
    RepairOrderPart.objects.bulk_update(rops_to_save,['status'])            
    
    ## update parts master last status
    parts_to_update = []
    
    for rop in rops:
        res = rop.update_part(commit=False)
        if res:
            parts_to_update.append(res)
    
    Parts.objects.bulk_update(parts_to_update,[
        'last_order_no',
        'last_check_in',
        'last_check_out',
        'last_status_details',
        'last_status',
        'last_order',
    ])

    
    
    ## update order status

    repair_order.complete_datetime = datetime.datetime.now()
    repair_order.complete_by = user
    repair_order.status = SELECTED_STATUS
    
    if force:
        statuss = check_all_status(repair_order)

    job_complete_payload = {
        "part_detail": ['no fault found'],
        "current_task": ['no fault found'],
        "software": ['no fault found'],
        "hardware": ['no fault found'],
        "edoc": ['no fault found'],
        "hard_time_replacement": ['no fault found'],
        "final_check": ['no fault found'],
    }
    description = f"{repair_order.order_no} is complete by {user}"

    primary_part = repair_order.primary_part

    if primary_part.is_parent:
        if force:
            job_complete_payload = {
                "part_detail": [],
                "current_task": [],
                "software": [],
                "hardware": [],
                "edoc": [],
                "hard_time_replacement": [],
                "final_check": [],
            }
            for key in statuss:
                if {"stock_code":primary_part.stock_code,"serial_no":primary_part.serial_no} in statuss[key]["log_parts"]:
                    job_complete_payload[key] = statuss[key]["message"]
        parent_log = Log(
                username=user,
                log_type=Log.LogType.MAINTENANCE,
                category_type=Log.CategoryType.ORDER,
                action_type=Log.ActionType.COMPLETE,
                action='job_complete',
                payload= json.dumps(job_complete_payload),
                description=description,
                repair_order=repair_order.id,
                parent_stock_code=primary_part.stock_code,
                parent_part=primary_part.serial_no,
                part='',
                stock_code=''
            )
        parent_log.save()

        for child in primary_part.children:
            if force:
                job_complete_payload = {
                    "part_detail": [],
                    "current_task": [],
                    "software": [],
                    "hardware": [],
                    "edoc": [],
                    "hard_time_replacement": [],
                    "final_check": [],
                }
                for key in statuss:
                    if {"stock_code":child.stock_code,"serial_no":child.serial_no} in statuss[key]["log_parts"]:
                        job_complete_payload[key] = statuss[key]["message"]
                    
            child_log = Log(
                username=user,
                log_type=Log.LogType.MAINTENANCE,
                category_type=Log.CategoryType.ORDER,
                action_type=Log.ActionType.COMPLETE,
                action='job_complete',
                payload= json.dumps(job_complete_payload),
                description=description,
                repair_order=repair_order.id,
                parent_stock_code=primary_part.stock_code,
                parent_part=primary_part.serial_no,
                part=child.serial_no,
                stock_code=child.stock_code
            )
            child_log.save()

    repair_order.save()

    return True,status

@transaction.atomic
def checkout(repair_order:Union[int,RepairOrder], workgroup, username):
    
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)

    ## check repair order status
    if repair_order.status not in [
        repair_order.STATUS.COMPLETE,
        repair_order.STATUS.FORCE_COMPLETE,
    ]:
        if repair_order.status == repair_order.STATUS.CHECKED_OUT:
            raise ValueError('Job is already checked out.')
        
        raise ValueError('Job is not in complete status, current job status: '+repair_order.status)

    if repair_order.check_out is not None:
        raise ValueError('Job is already checked out.')
    
    primary_part = repair_order.primary_part
    
    if primary_part.status not in [
        repair_order.STATUS.COMPLETE,
        repair_order.STATUS.FORCE_COMPLETE,
    ]:
        raise ValueError('Job is not complete.')           

    ## update repair_order checkout
    repair_order.check_out = datetime.datetime.now()
    # repair_order.status = repair_order.STATUS.CHECKED_OUT
    repair_order.save()
    
    
    ## TODO: update hardtime replacement
    
    ## update repair_order_part and checkout
    rops = repair_order.get_repair_order_part()
    parts_update = []
    rop_updates = []
    checkout_parts = []
    
    for r in rops:
        if r.part is not None:
            r.last_check_out = repair_order.check_out
            r.deliver_to = repair_order.deliver_to
            
            rop_updates.append(r)
            
            part_result = r.update_part(commit=True)
            parts_update.append(part_result)
            
            payload = {
            "last_order_no": part_result.last_order_no,
            "last_check_in": part_result.last_check_in.strftime("%m/%d/%Y, %H:%M:%S"),
            "last_check_out": part_result.last_check_out.strftime("%m/%d/%Y, %H:%M:%S"),
            "last_status_details": part_result.last_status_details,
            "last_status": part_result.last_status,
            "last_order": part_result.last_order.id,
            "workgroup": part_result.workgroup,
            "deliver_to": repair_order.deliver_to,
            }

            p_stock_code = primary_part.stock_code
            p_serial_no = primary_part.serial_no
            if p_stock_code == r.stock_code and p_serial_no == r.serial_no:
                p_stock_code=''
                p_serial_no=''
            
            checkout_parts.append(f"SC: {r.stock_code}, SN: {r.serial_no}")

            log = Log(
                username=username,
                log_type=Log.LogType.PARTMASTER,
                category_type=Log.CategoryType.ORDER,
                action_type=Log.ActionType.CHECKOUT,
                action='checkout__update_part_last_checkout_status',
                payload= json.dumps(payload),
                description="update part last status",
                repair_order=repair_order.id,
                parent_stock_code=p_stock_code,
                parent_part=p_serial_no,
                part=r.serial_no,
                stock_code=r.stock_code
            )
            log.save()
           
        
    RepairOrderPart.objects.bulk_update(rop_updates,['last_check_out'])
    
    Parts.objects.bulk_update(parts_update,[
        'last_order_no',
        'last_check_in',
        'last_check_out',
        'last_status_details',
        'last_status',
        'last_order'
    ])

    payload={
        "checkout_parts": checkout_parts
    }

    log = Log(
            username=username,
            log_type=Log.LogType.PARTMASTER,
            category_type=Log.CategoryType.ORDER,
            action_type=Log.ActionType.CHECKOUT,
            action='checkout',
            payload= json.dumps(payload),
            description="checkout parts",
            repair_order=repair_order.id,
            parent_stock_code=p_stock_code,
            parent_part=p_serial_no,
            part='',
            stock_code=''
        )
    log.save()

    return True

def check_relation_and_update_fitment(primary_part, repair_order, user):
    if primary_part.is_parent:
        ##origin_part_childs = Parts.objects.get(stock_code=primary_part.stock_code, serial_no=primary_part.serial_no).children
        order_part_parent = RepairOrderPart.objects.get(repair_order=repair_order.id,stock_code=primary_part.stock_code,serial_no=primary_part.serial_no)

        parent_part = Parts.objects.get(stock_code=order_part_parent.stock_code,serial_no=order_part_parent.serial_no)

        # origin_part_child_dict = {
        #     origin_part_child.sequence: origin_part_child
        #     for origin_part_child in parent_part.children
        # }
        ## clean origin relation
        for opc in parent_part.children:
            opc.parent_part = None
            opc.save()
            description=f"Update repair status to part master, clean the original relation SC: {opc.stock_code}, SN: {opc.serial_no}, sequence: {opc.sequence} from parent: SC: {parent_part.stock_code}, SN: {parent_part.serial_no}"

            payload={
                "repair_order":repair_order.id,
                "parent_stock_code":parent_part.stock_code,
                "parent_part":parent_part.serial_no,
                "serial_no":opc.serial_no,
                "stock_code":opc.stock_code
            }
            defitment_log = Log(
                username=user,
                log_type=Log.LogType.PARTMASTER,
                category_type=Log.CategoryType.PART,
                action_type=Log.ActionType.DEFITMENT,
                action='job_complete__update_part_hardtime_replacement',
                payload= json.dumps(payload),
                description=description,
                repair_order=repair_order.id,
                parent_stock_code=parent_part.stock_code,
                parent_part=parent_part.serial_no,
                part=opc.serial_no,
                stock_code=opc.stock_code
            )
            defitment_log.save()

        ## update relation
        for ropc in order_part_parent.children:
            if ropc.serial_no == '':
                continue
            c = Parts.objects.get(stock_code=ropc.stock_code,serial_no=ropc.serial_no,deleted=False)
            c.sequence = ropc.sequence
            c.parent_part = parent_part
            c.save()

            payload={
                "ropc_stock_code":ropc.stock_code,
                "ropc_serial_no":ropc.serial_no,
                "ropc_sequence":ropc.sequence,
            }

            description=f"Update repair status to part master, SC: {ropc.stock_code}, SN: {ropc.serial_no}, sequence: {ropc.sequence} is fit to parent: SC: {parent_part.stock_code}, SN: {parent_part.serial_no}"

            fitment_log = Log(
                username=user,
                log_type=Log.LogType.PARTMASTER,
                category_type=Log.CategoryType.PART,
                action_type=Log.ActionType.FITMENT,
                action='job_complete__update_part_hardtime_replacement',
                payload= json.dumps(payload),
                description=description,
                repair_order=repair_order.id,
                parent_stock_code=parent_part.stock_code,
                parent_part=parent_part.serial_no,
                part=ropc.serial_no,
                stock_code=ropc.stock_code
            )
            fitment_log.save()
            """ if ropc.sequence not in origin_part_child_dict:
                print('hello', ropc.stock_code,ropc.serial_no)
                c = Parts.objects.get(stock_code=ropc.stock_code,serial_no=ropc.serial_no)
                c.sequence = ropc.sequence
                c.parent_part = parent_part
                c.save()

            if ropc.sequence in origin_part_child_dict and origin_part_child_dict[ropc.sequence].serial_no != ropc.serial_no:
                ## update new child
                print('hello 123', ropc.stock_code,ropc.serial_no)

                c = Parts.objects.get(stock_code=ropc.stock_code,serial_no=ropc.serial_no)
                c.sequence = ropc.sequence
                c.parent_part = parent_part
                c.save() """
    

def check_imp_work(repair_order:Union[int,RepairOrder]):
    outstanding = 0
    messages = []
    log_parts = []
    if isinstance(repair_order,int):
        repair_order = RepairOrder.objects.get(id=repair_order)
    repair_order_parts = repair_order.get_repair_order_part()

    parts_dict = {
        p.stock_code : p
        for p in repair_order_parts
    }
    imp_works = RepairOrderImprovementWork.objects.filter(repair_order=repair_order)
    
    for e in imp_works:
        if e.complete == False:
            outstanding += 1
            messages.append(f'{parts_dict[e.stock_code].description}: Serial no: {e.serial_no}. Improvement Work: {e.doc_name} is not complete.')
            log_parts.append({"stock_code":e.stock_code,"serial_no":e.serial_no})
    return {
        'outstanding':outstanding,
        'message':messages,
        "log_parts": log_parts
    }
